#!/bin/bash
#ziconv.sh 批量转换文本文件的编码(encoding), 可以遍历子目录，可以指定文件名模板。
#Copyright (C) 2021-2022 Zhai YongNing 版权所有。
#本程序是自由软件：你可以根据自由软件基金会发布的GNU Affero通用公共许可证的条款，即许可证的第3版或（您选择的）任何后来的版本重新发布它和/或修改它。。
#本程序的发布是希望它能起到作用。但没有任何保证；甚至没有隐含的保证。本程序的分发是希望它是有用的，但没有任何保证，甚至没有隐含的适销对路或适合某一特定目的的保证。 参见 GNU Affero通用公共许可证了解更多细节。
#您应该已经收到了一份GNU Affero通用公共许可证的副本。 如果没有，请参见<https://www.gnu.org/licenses/>。
#还要增加如何通过电子和纸质邮件与您联系的信息。
#如果您的软件可以通过计算机网络与用户进行远程交互，您也应该确保它为用户提供一种获得其源代码的方法。例如，如果您的程序是一个网络应用程序，它的界面可以显示一个 "源代码 "的链接，引导用户进入代码的存档。您可以用很多方法提供源码，不同的解决方案对不同的程序会更好；具体要求见第13节。
#如果有必要，您还应该让您的雇主（如果您是程序员）或学校（如果有的话）为该程序签署一份 "版权免责声明"。有关这方面的更多信息，以及如何申请和遵守GNU AGPL，请参见<https://www.gnu.org/licenses/>。

#Ver:0.00.06 2022-12-29   修订，确保命令行中，参数"--"之后的是"无选项参数";'--help','--installself','--version'除外。
#Ver:0.99.05 2022-12-27   修订，接受含有':'的文件名及目录名 
#Writer: Zhai YongNing <zhaiyn-free@foxmail.com>

#Ver:0.99.02 2022-12-22 CST  第一个可发布版本 
#Writer: Zhai YongNing <zhaiyn-free@foxmail.com>
#
#    写这个小工具,起源于疫情防控；在居家期间，空闲时间太多，决定学习shell编程，算是自我能力提升，不浪费光阴吧。
#ziconv.sh就是编程的练习，它的功能来源于我的一些实际需要（近期经常写在同时在Linux及Windows上运行的同一代码),
#iconv直接批量处理文件仍显麻烦。同时为实践学到的编程技巧，强加了一些不必要功能，以模仿成熟的linux软件风格，
#如"复杂参数处理"、"自安装"，不一定实用；作为一个linux系统使用及shell编程入门新手，代码仍显稚嫩，对谬误之处，
#望不吝赐教。
#    其实，实现批量文件编码转换，较为简单或正统的做法是用"find + iconv",作者是在代码写到一大半时，才发现的,汗！
#不过它更多的是作为编程练习，用"find + iconv"方法来处理，起不到练习的目的。不过，我会另写一个find来重写一
#个ficonv,兼容ziconv,当然，又是一个对find使用的练习。

readonly __VERSION__="0.99.05" 

#Debug mode,only for developer
DebugMode=0

function DEBUG(){
	[ "$DebugMode" -eq 1 ] && ( "$@"  >&2) 
}

function ErrMessage(){
	echo -n "ERROR:" >&2 
	echo "$@" >&2 
}

function WarnMessage(){
	echo -n "WARN:" >&2 
	echo "$@" >&2 
}

function usage(){
# $1 帮助的索引参数的
    local tl=""
	local te=""
	tl="${LANG%%.*}"
	[ -z "$tl" ] && tl="$LANG"
	te="${LANG##*.}"
	if [ "$tl" != "zh_CN" ] ; then 
		WarnMessage	"ziconv.sh is writed with Simplified Chinese,can't be used in current language environment,sorry!"
		return 1
	fi 
	if [ "$(dirname "$(realpath "$0")")" != "$(realpath "$HOME/.local/bin")" ] && [ "$te" != "UTF-8" ] ; then
		WarnMessage "Please install it by './ziconv.sh -t <current text encoding> --installself' before use it."
		WarnMessage "And it must be sured that system language is zh_CN."
		return 1
	fi
	unset tl
	unset te
	cat << ___EOF___
Usage($1):
ziconv.sh (${AliasNameList[@]}) 
        [--help]  [-V]  [--version]  [--installself]
        [-T]  [-v]  [-y] [-s]
        [-f 文件的原编码]  [-t 转换后的文件编码]
        [-d 指定目录1 ... 指定目录N -p "模式1" ... "模式N" [-r [-L 最大目录搜索深度]]]
        [-F 指定文件...]
对指定目录下且文件名符合指定模式的文件、指定的文件进行文本编码转换。
    --help  帮助信息
    -V
    --version
            版本信息

    --installself
            安装自己到~/.local/bin,并创建别名${AliasNameList[@]}.
            在此过程中,会把ziconv.sh的编码转换为当前系统的编码,如果不能探测到当前系统编码，须用'-t'选项
            给予指定。
            注意:不能从已安装完成的脚本(~/.local/bin/)中再安装。不要手动改变ziconv.sh的编码。

    -T      设定测试模式。测试模式与正常模式相比，除了最终不改变原文件外，一切操作都相同。
            强烈建议，与-y选项联合使用,在批量操作前,先行检查指定的文件、目录、及模板是否与您的预期符合。

    -v      详细模式，暂时没有作用。

    -y      交互模式，如设定，在改变文件之前需要您的确认，并可查看文件清单。

    -s      安静模式，如设定，则不显示任何信息，包括警告。同时关闭-y选项。

    -f      指定待转换的文件的编码

    -t      指定转换后的文件的编码

    -d      指定目录，可以指定多个，可以用通配符(*)。如未指定文件名模板，目录指定无意义。

    -p      指定文件名模板，必须包括在双引号("")中,模板使用通配符方式，不是正则表达式；

    -r      开启目录遍历，即处理指定目录的子目录。

    -L      指定目录遍历深度,默认为10层,最大可设为30。指定目录为第0层。如果目录或模板未指定,尝试指定无意义。

    -F      另一种指定待转换文件的方式，可以使用通配符(*)，可以与-d及-p选项同时使用。
    不处理版本控制软件git的工作目录(.git),除非在-d选项中被直接指定。不处理目录的软链接，除非在-d中指定。不处理文件的软链接,除非是在-F中指定。
不能进入的目录及不能写的文件，不作处理。

___EOF___
	echo "别名等效:"
	local i
	for (( i=0 ;; i++ ));do
		[  -z "${AliasNameList[i]}"  ]  && break
		echo    "    ${AliasNameList[i]}"
		echo -n "        ziconv.sh"     
		[ -n "${AliasFromCodeList[i]}" ] && echo -n " -f ${AliasFromCodeList[i]}"
		[ -n "${AliasToCodeList[i]}" ]   && echo -n " -t ${AliasToCodeList[i]}"
		[ -n "${AliasDirList[i]}" ]      && echo -n " -d ${AliasDirList[i]}"
		[ -n "${AliasPatternList[i]}" ]  && echo -n " -p ${AliasPatternList[i]}"
		echo ""
	done
	unset i
	cat << ___EOF___

退出状态:
  0	正常
非0 存在错误

示例：
    ./ziconv.sh -t GBK --installself
      安装ziconv到~/.local/bin下，当前系统的文字编码是GBK。

    ziconv -f UTF-8 -t GBK -d dir_for_test -p "*.cpp" "*.txt" "*.c" -r
    utf8-gbk -d dir_for_test -p "*.cpp" -p "*.txt" -p "*.c" -r
      将'dir_for_test'目录下及其子目录下的，扩展名为'cpp'、'txt'、'c'的文件的编码从utf-8转换为gbk。

    utf8-gbk -d dir_for_test -p "*.txt" -r  -F dir_for_test/*.cpp 
      将'dir_for_test'目录下及其子目录下的，扩展名为'txt'的文件，及dir_for_test目录下扩展名为'cpp'文件的编码从utf-8转换为gbk。

待实现功能:
    1. 指定排除模板，用于在前述指定文件及目录中，排除部分的文件及目录。待后续版本完成。
已知其它缺陷:
    1. 代码的注释不全。
    2. VCS及CVS等其它版本控制工具的工作目录尚未排除，因为作者不知目录是什么名字。
测试情况:
    1. 对dir_for_test进行测试,未发现问题。
    2. 测试平台有Cygwin3.4,Msys2,Debian11,Fedora37,EndeavourOS。
代码库:
    https://gitee.com/zhaiyongning/ziconv.sh
如发现Bug,或改进建议,可联系 Zhai YongNing <zhaiyn-free@foxmail.com>. 
非常希望得到大家的帮助,让ziconv.sh更为成熟!
___EOF___
	return 0
}

function version_message() {
	cat  << ___EOF___
$(basename "$0") ( ziconv.sh $__VERSION__ )
Copyright (C) 2021-2022 Zhai YongNing 版权所有.
授权 AGPLv3+: GNU AGPL 第三版或后续版本 <https://www.gnu.org/licenses/agpl-3.0.txt>
此软件为自由软件：您可对其自由修改并重新发布。
本软件没有任何担保；包括没有适销性和某一专用目的下的适用性担保。
由 Zhai YongNing <zhaiyn-free@foxmail.com> 编写。
___EOF___
}

#别名配置表 
readonly -a AliasNameList=(       "gbk-utf8"    "utf8-gbk"        "win-linux"      "linux-win"        "ziconv" )
readonly -a AliasFromCodeList=(   "GBK"           "UTF-8"           "GBK"             "UTF-8"             ""   )
readonly -a AliasToCodeList=(     "UTF-8"         "GBK"             "UTF-8"            "GBK"              ""   )
readonly -a AliasDirList=(        ""              ""                ""                 ""                 ""   )
readonly -a AliasPatternList=(    ""              ""                ""                 ""                 ""   )
readonly -a AliasNoOptParamList=( ""              ""                ""                 ""                 ""   )

#-----自安装到~/.local/bin-----
function installself(){
	if [ ! -x "/usr/bin/iconv" ] ; then
		WarnMessage "Can't find iconv. Please install it."
		return 1 
	fi
	local tmplang="${LANG##*.}"
	tmplang="${tmplang^^}"
	if [ -n "$ToCode" ] ; then
		tmplang="${ToCode^^}"
	elif [ -z "$tmplang" ]	; then 
		WarnMessage "Can't know your encoding from LANG , please use   './ziconv -t <your encoding> --installself'  for install." 
		return 1
	fi
	if ! CodeIsValid "$tmplang"  "$tmplang" ; then
		WarnMessage  "Your encoding '$tmplang' is not valid! Please use 'iconv -l' for valid encoding."
		return 1
	fi
	local installpath
	installpath="$(realpath -Pm "$HOME/.local/bin")"
	local realfilename
	realfilename="$(realpath "$0")"
	[ "$(basename "$realfilename")" == "ziconv.sh" ] || ( echo "Name must be 'ziconv.sh'." ; return 1 )
	mkdir -p "$installpath" || ( echo "Create $installpath DIR failure." ; return 1 )
	local tmpobjname
	tmpobjname="$installpath/$(basename "$realfilename")"
	if [ "$realfilename" = "$(realpath "$tmpobjname")" ] ; then
		WarnMessage "Can't install me from '$installpath'" 
		return 1
	fi
	rm -rf "$tmpobjname" &>/dev/null || return 1
	if ! iconv -f utf-8 -t "$tmplang" < "$realfilename" >  "$tmpobjname" 2>/dev/null ; then
		 WarnMessage "Copy '$realfilename' to '$installpath' failure." 
		return 1
	else   
		chmod 500 "$tmpobjname" || return 1
	fi	
#		cp -f "$realfilename" "$installpath" > /dev/null || ( echo "复制$realfilename失败" ; return 1)
	
	echo "Install $(basename "$realfilename") to '$installpath' sucess."
	local i
	for (( i=0 ;; i++ ));do
	   [  -z "${AliasNameList[i]}"  ]  && break
 	   DEBUG echo "Name=${AliasNameList[i]} FromCode=${AliasFromCodeList[i]} ToCode=${AliasToCodeList[i]} "\
					"Dir=${AliasDirList[i]} Pattern=${AliasPatternList[i]} Reg=${AliasNoOptParamList[i]}"
		if ! ln -sf "$(basename "$realfilename")" "$installpath/${AliasNameList[i]}" &> /dev/null ; then
			WarnMessage "Creat aliasname ${AliasNameList[i]} failure." 
			return 1
		else
			echo "Creat aliasname ${AliasNameList[i]} sucess."
		fi
	done
	unset i
	if [[  ":$PATH:" = *:$installpath:*   ]] ;then
		:
	elif ! [[   ":$(bash -ic 'echo "$PATH" >&100' 100>&1 1>/dev/null):" = *:$installpath:*   ]] ;then
		if [ -f "$HOME/.bashrc" ] ; then 
			local profilename="$HOME/.bashrc"
		else
		    echo "Can't find ~/.bashrc , PATH set is failure."
			return 1	
		fi

		cat >> "$profilename" << ___EOF___	  

# set PATH so it includes user's private bin if it exists
if [ -d "\$HOME/.local/bin" ] ; then
	if ! [[ ":\$PATH:" =~ ":\$HOME/.local/bin:" ]] ; then
		PATH="\$HOME/.local/bin:\$PATH"
		export PATH
	fi
fi
___EOF___
		echo -e "PATH set is sucess. Before restart bash,use '~/.local/$(basename "$realfilename")'"
	fi
	return 0
}

function CodeIsValid() {
	local OldIFS
	OldIFS="$IFS"
	IFS=$' '$'\n'$'\t'$'/'
	if [ $# -ne 2 ]; then
		return 1
	fi
	local ok1=1
	local ok2=1
	for code in $(iconv -l); do
		if [ $ok1 -eq 0 ] && [ $ok2 -eq 0 ]; then
			break
		fi
		if [ $ok1 -eq 1 ] && [ "$code" == "$1" ]; then
			ok1=0
		fi
		if [ $ok2 -eq 1 ] && [ "$code" == "$2" ]; then
			ok2=0
		fi
	done
	IFS="$OldIFS"
	return $(( ok1 + $(( ok2 * 2)) ))
}

# shellcheck disable=SC2317  # Don't warn about unreachable commands in this function
function show_param( ) {
	echo FromCode="$FromCode" 
	echo ToCode="$ToCode"
	echo Recursion="$Recursion"
	echo BaseFileNamePatternList="${BaseFileNamePatternList/$'\r'/':'}"
	echo DirList="${DirList/$'\r'/':'}"
	echo FileList="${FileList/$'\r'/':'}"
	echo TestMode="$TestMode"
	echo Silent="$Silent"
	echo YesNo="$YesNo"
	echo Verbose="$Verbose"
	echo DeepLimit="$DeepLimit"
	echo NoOptParam="${NoOptParam/$'\r'/':'}"
	echo HelpSign="$HelpSign"
	echo InstallSign="$InstallSign"
	echo VersionSign="$VersionSign"
	echo DebugMode="$DebugMode"
	echo NoOptParamStat="$NoOptParamStat"
	return 0
}

#----开始-----
ExitCode=0
FromCode=""
ToCode=""
Recursion=0
BaseFileNamePatternList=""
DirList=""
FileList=""
TestMode=0
Silent=0
YesNo=0
Verbose=0
DeepLimit=10
NoOptParam=""
HelpSign=""
InstallSign=""
VersionSign=0

#-----检查入口参数数量-----
[ "$#" -eq 0 ] &&  HelpSign='-'

#-----从命令行参数分析有效选项及参数----
NoOptParamStat=0
CurrentParamSign='-'
while [ -n "$1" ]; do
	while getopts :VTsyrf:t:vd:p:F:L:-: opt; do
		case "$opt" in
		V) 
			VersionSign=1
			CurrentParamSign='V'
			DEBUG echo "-V"
			;; 
		T)
			DEBUG echo -T 
			CurrentParamSign='T'
			TestMode=1
			;;
		s)
			DEBUG echo -s 
			CurrentParamSign='s'
			Silent=1
			;;
		y)
			DEBUG echo -y 
			CurrentParamSign='y'
			YesNo=1
			;;
		r)
			DEBUG echo -r 
			CurrentParamSign='r'
			Recursion=1
			;;
		f)
			DEBUG echo -f 
			CurrentParamSign='f'
			if [ "$OPTARG" = "--help" ] ; then 
				HelpSign="$CurrentParamSign"
				DEBUG echo "-help($HelpSign)" 
				CurrentParamSign='h'
			fi 
			if [ "$OPTARG" = "--installself" ] ;then 
				DEBUG echo "-installself"
				InstallSign=1
				CurrentParamSign="installself"
			fi
			if [ "$OPTARG" = "--version" ] ;then 
				DEBUG echo "-version"
				VersionSign=1
				CurrentParamSign="V"
			fi
			FromCode="${OPTARG^^}"
			;;
		t)
			DEBUG echo -t 
			CurrentParamSign='t'
			if [ "$OPTARG" = "--help" ] ; then 
				HelpSign="$CurrentParamSign"
				DEBUG echo "-help($HelpSign)" 
				CurrentParamSign='h'
			fi 
			if [ "$OPTARG" = "--installself" ] ;then 
				DEBUG echo "-installself"
				InstallSign=1
				CurrentParamSign="installself"
			fi
			if [ "$OPTARG" = "--version" ] ;then 
				DEBUG echo "-version"
				VersionSign=1
				CurrentParamSign="V"
			fi
			ToCode="${OPTARG^^}"
			;;
		v)
			DEBUG echo -v 
			CurrentParamSign='v'
			Verbose=1
			;;
		d)
			DEBUG echo -d 
			CurrentParamSign='d'
			if [ "$OPTARG" = "--help" ] ; then 
				HelpSign="$CurrentParamSign"
				DEBUG echo "-help($HelpSign)" 
				CurrentParamSign='h'
			fi 
			if [ "$OPTARG" = "--installself" ] ;then 
				DEBUG echo "-installself"
				InstallSign=1
				CurrentParamSign="installself"
			fi
			if [ "$OPTARG" = "--version" ] ;then 
				DEBUG echo "-version"
				VersionSign=1
				CurrentParamSign="V"
			fi
			if [ -n "$DirList" ]; then
				DirList="$DirList"$'r'
			fi
			DirList="$DirList$(realpath -Pm "$OPTARG")"
			;;
		p)
			DEBUG echo -p 
			CurrentParamSign='p'
			if [ "$OPTARG" = "--help" ] ; then 
				HelpSign="$CurrentParamSign"
				DEBUG echo "-help($HelpSign)" 
				CurrentParamSign='h'
			fi 
			if [ "$OPTARG" = "--installself" ] ;then 
				DEBUG echo "-installself"
				InstallSign=1
				CurrentParamSign="installself"
			fi
			if [ "$OPTARG" = "--version" ] ;then 
				DEBUG echo "-version"
				VersionSign=1
				CurrentParamSign="V"
			fi
			if [ -n "$BaseFileNamePatternList" ]; then
				BaseFileNamePatternList="$BaseFileNamePatternList"$'\r'
			fi
			BaseFileNamePatternList="$BaseFileNamePatternList$OPTARG"
			;;
		F)
			CurrentParamSign='F'
			DEBUG echo "-$CurrentParamSign"
			if [ "$OPTARG" = "--help" ] ; then 
				HelpSign="$CurrentParamSign"
				DEBUG echo "-help($HelpSign)" 
				CurrentParamSign='h'
			fi 
			if [ "$OPTARG" = "--installself" ] ;then 
				DEBUG echo "-installself"
				InstallSign=1
				CurrentParamSign="installself"
			fi
			if [ "$OPTARG" = "--version" ] ;then 
				DEBUG echo "-version"
				VersionSign=1
				CurrentParamSign="V"
			fi
			if [ -n "$FileList" ]; then
				FileList="$FileList"$'\r'
			fi
			FileList="$FileList$(realpath -Pm "$OPTARG")"
			;;
		L)
			CurrentParamSign='L'
			DEBUG echo "-$CurrentParamSign"
			if [ "$OPTARG" = "--help" ] ; then 
				HelpSign="$CurrentParamSign"
				DEBUG echo "-help($HelpSign)" 
				CurrentParamSign='h'
			fi 
			if [ "$OPTARG" = "--installself" ] ;then 
				DEBUG echo "-installself"
				InstallSign=1
				CurrentParamSign="installself"
			fi
			if [ "$OPTARG" = "--version" ] ;then 
				DEBUG echo "-version"
				VersionSign=1
				CurrentParamSign="V"
			fi
			[ -n "$OPTARG" ] && DeepLimit="$OPTARG"
			;;
		

		-)  
			DEBUG echo "--$OPTARG" 
		    case "$OPTARG" in
			"help")
					HelpSign="$CurrentParamSign"
					DEBUG echo "-help($HelpSign)" 
					CurrentParamSign='h'
					;;
			"installself")
					DEBUG echo "-installself"
					InstallSign=1
					CurrentParamSign="installself"
					;;
			"version")
					DEBUG echo "-version"
					VersionSign=1
					CurrentParamSign="V"
					;;
			*)
					DEBUG echo "未知的long opt:$OPTARG"
					CurrentParamSign="-"
			esac
			;;
		*)
			DEBUG echo "未知的opt:$opt"
			CurrentParamSign='-'
			;;
		esac
	done
	OPTIND=$((OPTIND-1))
	#下面一行，是实现功能：在 -- 后的参数一律作为长参数，不再考虑其是否为短选项。 
	[ "${!OPTIND}" = "--" ] && { NoOptParamStat=1 ; CurrentParamSign="*" ; }
	shift $OPTIND
	OPTIND=1
	while [ -n "$1" ] ; do
		if [ "$1" = "--help" ] ; then
			HelpSign="$CurrentParamSign"
			DEBUG echo "--help($HelpSign)" 
			CurrentParamSign='h'
		elif [ "$1" = "--installself" ] ; then
				DEBUG echo "--installself"
				InstallSign=1
			    CurrentParamSign='installself'
		elif [ "$1" = "--version" ] ; then
				DEBUG echo "--version"
				VersionSign=1
			    CurrentParamSign='V'
		else
			case $CurrentParamSign in
			d)
				if [ -n "$DirList" ]; then
					DirList="$DirList"$'\r'
				fi
				DirList="$DirList$(realpath -Pm "$1")"
				;;
			p)
				if [ -n "$BaseFileNamePatternList" ]; then
					BaseFileNamePatternList="$BaseFileNamePatternList"$'\r'
				fi
				BaseFileNamePatternList="$BaseFileNamePatternList$1"
				;;
			F)
				if [ -n "$FileList" ]; then
					FileList="$FileList"$'\r'
				fi
				FileList="$FileList$(realpath -Pm "$1")"
				;;
			*) NoOptParam="$1"$'\r'"$NoOptParam" ;;
			esac
		fi
		shift
		[ $NoOptParamStat -eq 0 ] && break
	done
done

#-----处理帮助、版本信息及安装-----
if [  -n "$HelpSign" ] ; then 
	usage "$HelpSign" 
	exit $? 
fi
if [ "$VersionSign" -ne 0 ] ; then
	version_message
	exit $?
fi
if [  -n "$InstallSign" ] ; then 
	installself
	exit $?
fi

#-----分析执行文件名，实现不同别名对应的文件编码及选项-----
tmpbn="$(basename "$0")"
for (( tmpi=0 ;; tmpi++)); do
  	[ -z "${AliasNameList[tmpi]}" ] && break   
	if [ "${AliasNameList[tmpi]}" == "$tmpbn" ]; then
		[ -n "${AliasFromCodeList[tmpi]}" ] && FromCode="${AliasFromCodeList[tmpi]}"
		[ -n "${AliasToCodeList[tmpi]}" ] && ToCode="${AliasToCodeList[tmpi]}"
		if [ -n "${AliasPatternList[tmpi]}" ] ; then
			[ -n "$BaseFileNamePatternList" ] && BaseFileNamePatternList="$BaseFileNamePatternList"$'\r'
			BaseFileNamePatternList="$BaseFileNamePatternList${AliasPatternList[tmpi]}"
		fi	
		if [ -n "${AliasDirList[tmpi]}" ] ; then
			[ -n "$DirList" ] && DirList="$DirList"$'\r'
			DirList="$DirList$(realpath -Pm "${AliasDirList[tmpi]}")"
		fi
		if [ -n "${Alias[tmpi]}" ] ; then
			[ -n "$DirList" ] && DirList="$DirList"$'\r'
			DirList="$DirList$(realpath -Pm "${AliasDirList[tmpi]}")"
		fi
		NoOptParam="${AliasNoOptParamList[tmpi]}"$'\r'"$NoOptParam"
		break
	fi 
done
if [ -z "${AliasNameList[tmpi]}" ] && [ "$tmpbn" != "ziconv.sh" ] ; then
	 echo "合法的命令:${AliasNameList[*]} ziconv.sh" 
	 exit 1 
fi
unset tmpbn
unset tmpi

#-----处理非功能选项，重新设定-----
if [ $TestMode -eq 1 ] ;  then
	Silent=0
fi

if [ $Silent -eq 1 ]; then
	Verbose=0
	YesNo=0
	exec > /dev/null
	exec 2> /dev/null
fi

if [ $Verbose -ne 0 ]; then
	exec 51>&1
else
	exec 51>/dev/null
fi
DEBUG show_param 

#-----检查iconv工具是否存在-----
if  [ ! -x "/bin/iconv" ]; then
	ErrMessage "没有找到iconv,请安装iconv" 
	ExitCode=1
fi
#-----检查file工具是否存在-----
if  [ ! -x "/bin/file" ]; then
	WarnMessage "没有找到file,建议请安装file,以提供更多的失败信息"
	FileToolExist=0
else
	FileToolExist=1	
fi
#-----分析功能性选项及参数合法性-----
if [ -z "$FromCode" ]; then
	ErrMessage "原编码没有指定，请用-f选项指定"
	ExitCode=1
fi
if [ -z "$ToCode" ]; then
	ErrMessage "目标编码没有指定，请用-t选项指定"
	ExitCode=1
fi
if  { [ -z "$DirList" ] || [ -z "$BaseFileNamePatternList" ] ; } && [ -z "$FileList" ] ; then
	ErrMessage "没有指定转换的文件,可用-d选项指定目录并用-p指定文件名模板,或用-F指定文件名."
	ExitCode=1
fi
#-----检查目录遍历最大深度限值-----
if [ -n "$DirList" ] && [ -n "$BaseFileNamePatternList" ] && [ "$Recursion" -ne 0 ] ; then 
	if [[ ! "$DeepLimit" =~ ^[[:digit:]]+$  ]] || [ ! "$DeepLimit" -ge 1 ] ||  [ ! "$DeepLimit" -le 30 ] ; then
		ErrMessage "-L 选项的参数($DeepLimit)无效,请见帮助信息."
		ExitCode=1
	fi
fi
#---------检查错误码--------
[ "$ExitCode" -ne 0 ] && exit "$ExitCode" 
#-----检查指定的文件编码是否受iconv支持------
CodeIsValid "$FromCode" "$ToCode"
tmpexitcode=$?
if [ $tmpexitcode -eq 1 ] || [ $tmpexitcode -eq 3 ]; then
	ErrMessage "-f 选项的参数($FromCode)无效,请使用'iconv -l'命令查看可用编码." 
fi
if [ $tmpexitcode -eq 2 ] || [ $tmpexitcode -eq 3 ]; then
	ErrMessage "-t 选项的参数($ToCode)无效,请使用'iconv -l'命令查看可用编码." 
fi
[ $tmpexitcode -ne 0 ] &&	ExitCode=1	
unset tmpexitcode
#---------检查错误码--------
[ "$ExitCode" -ne 0 ] && exit "$ExitCode" 

#---将指定目录及子目录的文件加入待处理文件列表---
function CreateReadyFileList() {
	# 入口参数:
	# $1 DirList
	# $2 BaseFileNamePatternList
	# $3 Recursion
	# $4 Silent
	# $5 YesNo
	# $6 Verbose
	# $7 NoOptParam
	# $8 成功文件清单文件名 
	# $9 成功目录清单文件名 
	# $10 不成功文件清单文件名 
	# $11 不成功目录清单文件名 
	# 执行出口
	# 0 正常
	# 1 异常
	# 参数出口:
	# 待处理的目录，格式同DirList
	DEBUG echo "进入CreateList函数" 
	local inDirList="$1"
	DEBUG echo  "inDirList=${inDirList/$'\r'/':'}" 
	local inBaseFileNamePatternList="$2"
	DEBUG echo  "inBaseFileNamePatternList=${inBaseFileNamePatternList/$'\r'/':'}" 
	local inRecursion="$3"
	DEBUG echo "inRecursion=$inRecursion" 
	local inSilent="$4"
	DEBUG echo  "inSilent=$inSilent" 
	local inYesNo="$5"
	DEBUG echo "inYesNo=$inYesNo" 
	local inVerbose="$6"
	DEBUG echo "inVerbose=$inVerbose" 
	local inNoOptParam="$7"
    DEBUG echo "inNoOptParam=${inNoOptParam/$'\r'/':'}"
	local yesCFL="$8"
	local yesCDL="$9"
	local noCFL="${10}"
	local noCDL="${11}"
	local inDeepOut="${12}"

	local nextDirList=""
	local dn
	local fn

	local oldIFS="$IFS"
	IFS=$'\r'
	for dn in $inDirList ; do
		DEBUG echo "dn-:$dn:"
		if [ "$inDeepOut" -ne 0 ] ; then
			echo "$dn:目录遍历深度超过限值，不再处理该目录" >>"$noCDL"
			continue
		fi
		if [ ! -d "$dn" ]; then
			echo "$dn:是在-d选项下指定的非目录,不进行转换" >>"$noCDL"
			continue
		fi
		if [ -h "$dn" ] || [ ! -x "$dn" ] || [ ! -w "$dn" ]; then
			echo "$dn:目录不可进入、或不可写、或是符号链接" >>"$noCDL"
			continue
		fi
		echo "$dn" >>"$yesCDL"

		cd "$dn" 2>/dev/null || DEBUG echo ""
		if [ "$inRecursion" -eq 1 ] ; then
			for fn in * .* ; do
				if [ -d "$fn" ]; then
					if [ "$fn" = ".git" ] || [ "$fn" = "." ] || [ "$fn" = ".." ]; then
						continue
					fi
					[ -n "$nextDirList" ] && nextDirList="$nextDirList"$'\r'
					nextDirList="$nextDirList$dn/$fn"
				fi
			done
		fi
		for fn in $inBaseFileNamePatternList ; do
			if [ -f "$fn" ]; then
				if [ ! -h "$fn" ] && [ -w "$fn" ]; then
					echo "$dn/$fn" >>"$yesCFL"
				else
					echo "$dn/$fn:文件不可写、或是符号链接" >>"$noCFL"
				fi
			fi
		done
		cd - &>/dev/null || DEBUG echo "" 
	done
	IFS="$oldIFS"
	echo "${nextDirList}"
	return 0
}

#---将直接指定的文件加入待处理文件列表---
# shellcheck disable=SC2317  # Don't warn about unreachable commands in this file
function AppendToReadyFileList() {
	# 入口参数:
	# $1 FileList
	# $2 Silent
	# $3 YesNo
	# $4 Verbose
	# $5 yesCFL
	# $6 noCFL
	# 执行出口
	# 0 正常
	# 1 异常
	# 参数出口:
	# 无 
	DEBUG echo "进入AppendToReadyFileList函数" 
	local inFileList="$1"
	DEBUG echo  "inFileList=${inFileList/$'\r'/':'}" 
	local inSilent="$2"
	DEBUG echo  "inSilent=$inSilent" 
	local inYesNo="$3"
	DEBUG echo "inYesNo=$inYesNo" 
	local inVerbose="$4"
	DEBUG echo "inVerbose=$inVerbose" 
	local yesCFL="$5"
	local noCFL="$6"

	local fn
	local oldIFS="$IFS"
	IFS=$'\r'
	for fn in $inFileList ; do
		DEBUG echo "fn=$fn"
		if [ ! -w "$fn" ] || [ ! -f "$fn" ]; then
			echo "$fn:是-F选项下指定的文件,不存在或不可写" >>"$noCFL"
		else
			echo "$fn" >>"$yesCFL"
		fi
	done
	IFS="$oldIFS"
	return 0
}

function CodeConvert(){
	# $1 待转文件清单文件名
	# $2 fcode
	# $3 tcode
	# $4 成功转换文件清单文件名
    # $5 转换失败文件清单文件名
	# $6 成功数量，参数中保存的是返回用的变量的名称
	# $7 失败数量，参数中保存的是返回用的变量的名称
	# $8 测试模式
	local tmpfile
	tmpfile=$(mktemp)
	[ -z "$tmpfile" ] && return 1
	local fn=""
	local SucessCounter=0
	local FaultCounter=0
	echo  -n "" > "$4"
	echo  -n "" > "$5"
    while read -r fn   
     do
	 [ -z "$fn" ] && continue 
	 if iconv -f "$2" -t "$3" "$fn"  1>"$tmpfile"  2>/dev/null ; then
		DEBUG echo "Convert Sucess:$fn"
		echo "$fn" >> "$4"
		[ "$8" -eq  0 ] && (cat "$tmpfile" > "$fn") 
		((SucessCounter++))
	else	
		((FaultCounter++))
		echo -n "iconv转换失败--->" >> "$5"
		if [ "$FileToolExist" -ne 0 ] ; then 
			file  "$fn" >> "$5"
			else
			echo "$fn" >> "$5"
		fi
	 fi
	done < "$1"

	(("$6"=SucessCounter))
	(("$7"=FaultCounter))
	rm -rf "$tmpfile" >/dev/null
	[ "$FaultCounter" -ne 0 ] && return 1
	return 0
}

function RemoveRepeatLine() 
{
	# $1 清单文件名
	# $2 返回行数，参数中保存的是返回用的变量的名称
	
	local tmpfile
	tmpfile=$(mktemp)
	[ -z "$tmpfile" ] && return 1
	sort -bV "$1" -o "$tmpfile"  &> /dev/null ||  return 1
    local Fline=""
	local Line=""
	local LineCounter=0
	echo  -n "" > "$1"
    while read -r Line  
     do
	 if [ "$Line" != "$Fline" ]; then
		echo "$Line" >> "$1"
		Fline="$Line"
		((LineCounter++))
	 fi
	done < "$tmpfile"
	(("$2"=LineCounter+"$2"))
	rm -rf "$tmpfile" &>/dev/null
	return 0
}

DEBUG show_param 

[ "$TestMode" -eq 1 ] &&  WarnMessage "测试模式，文件不作任何改变。"
echo "将指定目录下文件及指定文件的编码从$FromCode转换到$ToCode"
if [ -n "$DirList" ] && [ -n "$BaseFileNamePatternList" ] ; then
	echo -n " 指定目录:${DirList/$'\r'/':'} "
	[ "$Recursion" -ne 0 ] && echo -n "及其子目录"
	echo " [模板:${BaseFileNamePatternList/$'\r'/':'}]"
fi
[ -n "$FileList" ] && echo " 指定文件${FileList/$'\r'/':'}"

TmpNameOfConvertedFileList=$(mktemp)
DEBUG echo TmpNameOfConvertedFileList:"$TmpNameOfConvertedFileList" 
NumOfConvertedFile=0

TmpNameOfConvertedDirList=$(mktemp)
DEBUG echo TmpNameOfConvertedDirList:"$TmpNameOfConvertedDirList" 
NumOfConvertedDir=0

TmpNameOfNoConvertedFileList=$(mktemp)
DEBUG echo TmpNameOfNoConvertedFileList:"$TmpNameOfNoConvertedFileList" 
NumOfNoConvertedFile=0

TmpNameOfNoConvertedDirList=$(mktemp)
DEBUG echo TmpNameOfNoConvertedDirList:"$TmpNameOfNoConvertedDirList" 
NumOfNoConvertedDir=0

if [ -n "$DirList" ] && [ -n "$BaseFileNamePatternList" ] ; then 
	echo "扫描指定的目录---->" >&51
	tmpdeepcounter=0
	DeepOut=0
	while [ -n "$DirList" ]; do
		DEBUG echo "DIRLIST=${DirList/$'\r'/':'}"
		[ "$tmpdeepcounter" -gt "$DeepLimit" ] && DeepOut=1 
		if [ "$DeepOut" -ne 0 ] ; then
			WarnMessage  "目录遍历深度已达$tmpdeepcounter,最大限值为$DeepLimit,不再遍历目录(${DirList/$'\r'/':'})"
		fi
		DirList=$(CreateReadyFileList "$DirList" "$BaseFileNamePatternList" "$Recursion" "$Silent" "$YesNo" "$Verbose" "$NoOptParam"\
		"$TmpNameOfConvertedFileList" "$TmpNameOfConvertedDirList" "$TmpNameOfNoConvertedFileList" "$TmpNameOfNoConvertedDirList"\
		"$DeepOut")
		(( tmpdeepcounter++ ))
	done
	unset tmpdeepcounter
	unset DeepOut
fi

if [ -n "$FileList" ] ; then
	echo "扫描指定的文件---->" >&51
	DEBUG echo "FileLIST=${FileList/$'\r'/':'}"
	AppendToReadyFileList "$FileList" "$Silent" "$YesNo" "$Verbose" "$TmpNameOfConvertedFileList" "$TmpNameOfNoConvertedFileList"
fi

echo "正在整理数据---->" >&51
RemoveRepeatLine "$TmpNameOfConvertedFileList" "NumOfConvertedFile" 
RemoveRepeatLine "$TmpNameOfNoConvertedFileList" "NumOfNoConvertedFile" 
RemoveRepeatLine "$TmpNameOfConvertedDirList" "NumOfConvertedDir" 
RemoveRepeatLine "$TmpNameOfNoConvertedDirList" "NumOfNoConvertedDir" 

echo "拟转换的文件数   $NumOfConvertedFile"  ; cat "$TmpNameOfConvertedFileList" >&51 
echo "拟处理的目录数   $NumOfConvertedDir"  ; cat "$TmpNameOfConvertedDirList" >&51
[ "$NumOfNoConvertedFile" -ne 0 ] && ( echo "无法处理的文件数 $NumOfNoConvertedFile"  ; cat "$TmpNameOfNoConvertedFileList") 
[ "$NumOfNoConvertedDir"  -ne 0 ] && ( echo "无法处理的目录数 $NumOfNoConvertedDir"   ; cat "$TmpNameOfNoConvertedDirList") 

NumOfSucessConvert=0
TmpNameOfSucessConvertFilelist="$(mktemp)"
NumOfFaultConvert=0
TmpNameOfFaultConvertFilelist="$(mktemp)"
if [ $YesNo -eq 1 ]; then
	YesNoResult="?"
else
	YesNoResult="Y"
fi
while true ;do
	[ "$YesNoResult" = "Y" ] || ! read -n1 -rs -p "请选择: F_待转文件 D_拟处理目录 f_不转换文件 d_不处理目录 ------  转换(Yy)退出(Qq):" YesNoResult || echo "$YesNoResult"
	case $YesNoResult in
		F) 	cat "$TmpNameOfConvertedFileList";; 
		f) 	cat "$TmpNameOfNoConvertedFileList";;
		D) 	cat "$TmpNameOfConvertedDirList" ;;
		d) 	cat "$TmpNameOfNoConvertedDirList";;
		Y|y) 
			echo "正在转换--->"
			CodeConvert "$TmpNameOfConvertedFileList" "$FromCode" "$ToCode" "$TmpNameOfSucessConvertFilelist" "$TmpNameOfFaultConvertFilelist"\
							"NumOfSucessConvert" "NumOfFaultConvert" "$TestMode"
			echo "成功数量:$NumOfSucessConvert"
			cat "$TmpNameOfSucessConvertFilelist" >&51
			echo "失败数量:$NumOfFaultConvert"
			cat  "$TmpNameOfFaultConvertFilelist"
			break;;
		Q|q) break;;
	*) ;;
	esac
done 

rm -rf "$TmpNameOfConvertedFileList" "$TmpNameOfConvertedDirList"\
		"$TmpNameOfNoConvertedFileList" "$TmpNameOfNoConvertedDirList"\
		"$TmpNameOfSucessConvertFilelist" "$TmpNameOfFaultConvertFilelist"\
		 &>/dev/null
echo -e "--->完成" 
exit 0
